package com.wkm.chainchain.blockchain;

import com.alibaba.fastjson.JSON;
import com.wkm.chainchain.blockchain.block.Block;
import com.wkm.chainchain.blockchain.block.Unit;
import com.wkm.chainchain.blockchain.util.RocksDBUtil;
import org.rocksdb.RocksDBException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.wkm.chainchain.blockchain.block.UnitPool.addUnit;
import static com.wkm.chainchain.blockchain.block.UnitPool.getBodyUnitList;
import static com.wkm.chainchain.blockchain.constant.ConstVariable.*;
import static com.wkm.chainchain.blockchain.util.BlockUtil.constructBlock;

public class BlockChainOp {

    private static final Logger log = LoggerFactory.getLogger(BlockChainOp.class);

    public static boolean insertData(Unit unit) {
        boolean genBlockFlag = addUnit(unit);
        if (genBlockFlag) {
            // 要生成一个区块
            ArrayList<Unit> unitList = getBodyUnitList();
            Block block = constructBlock(unitList);
            boolean saveRes = saveBlock(block);
            if (saveRes) {
                log.info("第[{}]个区块保存成功。", block.getHeader().getHeight());
            } else {
                log.info("第[{}]个区块保存失败。", block.getHeader().getHeight());
            }
        }
        return true;
    }

    public static Block queryBlock(String height) {
        try {
            byte[] bytes = RocksDBUtil.get(BLOCK_CF_NAME, height);
            if (bytes != null) {
                Block block = JSON.parseObject(new String(bytes), Block.class);
                return block;
            }
        } catch (RocksDBException e) {
            throw new RuntimeException(e);
        }
        log.info("区块查询失败，高度为：{}", height);
        return null;
    }

    public static int queryHeight() {
        try {
            String height = new String(RocksDBUtil.get(BLOCK_CF_NAME, NOW_HEIGHT));
            return Integer.parseInt(height);
        } catch (RocksDBException e) {
            throw new RuntimeException(e);
        }
    }

    public static int queryFileAddr(String fileAddr) {
        try {
            String height = new String(RocksDBUtil.get(UNIT_CF_NAME, fileAddr));
            return Integer.parseInt(height);
        } catch (RocksDBException e) {
            throw new RuntimeException(e);
        }
    }

    private static boolean saveBlock(Block block) {
        log.info("正在进行区块的保存：{}", block);
        if (!validate(block)) {
            log.info("当前区块校验不合法，无法保存！");
            return false;
        }
        try {
            Map<String, String> map = new HashMap<>();
            String refreshedHeight = block.getHeader().getHeight() + "";
            // 存入高度key对应的区块
            map.put(refreshedHeight, JSON.toJSONString(block));
            // 存入NOW_HEIGHT对应的新高度
            map.put(NOW_HEIGHT, refreshedHeight);
            // 批量把区块和新高度给put进去
            RocksDBUtil.batchPut(BLOCK_CF_NAME, map);

            // 存入当前所有unit对应的新的高度
            HashMap<String, String> unitMap = new HashMap<>();
            List<String> addrList = block.getBody().getUnits().stream().map(Unit::getFileAddr).collect(Collectors.toList());
            for (String addr : addrList) {
                unitMap.put(addr, refreshedHeight);
            }
            // 批量把文件路径和新高度给put进去
            RocksDBUtil.batchPut(UNIT_CF_NAME, unitMap);
        } catch (RocksDBException e) {
            throw new RuntimeException(e);
        }
        return true;
    }

    /**
     * 检查待保存的区块是否合格
     *
     * @param block 输入区块
     * @return 返回是否有效
     */
    private static boolean validate(Block block) {
        if (block == null || block.getHeader() == null || block.getBody() == null) {
            log.info("区块不合格，存在空指针部分：{}", block);
            return false;
        }
        int height = block.getHeader().getHeight();
        try {
            byte[] pre = RocksDBUtil.get(BLOCK_CF_NAME, height + "");
            if (pre != null) {
                // 已经存在当前高度的区块，冲突，报错
                log.info("已经存在当前高度的区块，当前高度不合法：{}", height);
                return false;
            }
            if (height == 1) {
                // 如果是创世区块，直接返回true
                return true;
            }
            byte[] preBlockByte = RocksDBUtil.get(BLOCK_CF_NAME, height - 1 + "");
            Block preBlock = JSON.parseObject(new String(preBlockByte), Block.class);
            // 从当前区块的区块头中获取hashPrevBlock
            String hashPrevBlock = block.getHeader().getHashPrevBlock();
            // 判断上一个区块的哈希值和hashPrevBlock是否相等
            if (!preBlock.getHeader().getBlockHash().equals(hashPrevBlock)) {
                // 不相等，说明当前区块的区块头不合法
                log.info("当前区块的区块头不合法：{}", block.getHeader());
                return false;
            }
        } catch (RocksDBException e) {
            throw new RuntimeException(e);
        }
        return true;
    }
}
